// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package org.jetbrains.kotlin.asJava

import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.externalSystem.service.project.ProjectDataManager
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.UsefulTestCase
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
import org.jetbrains.kotlin.asJava.elements.KtLightPsiArrayInitializerMemberValue
import org.jetbrains.kotlin.idea.base.plugin.artifacts.TestKotlinArtifacts
import org.jetbrains.kotlin.idea.compiler.configuration.IdeKotlinVersion
import org.jetbrains.kotlin.idea.completion.test.assertInstanceOf
import org.jetbrains.kotlin.idea.facet.configureFacet
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.test.KotlinJdkAndLibraryProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.junit.internal.runners.JUnit38ClassRunner
import org.junit.runner.RunWith

@RunWith(JUnit38ClassRunner::class)
class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {

    override fun getProjectDescriptor(): LightProjectDescriptor =
        KotlinJdkAndLibraryProjectDescriptor(TestKotlinArtifacts.kotlinStdlib)

    fun testIsHiddenByDeprecated() {
        myFixture.configureByText(
            "test.kt", """
            import kotlin.DeprecationLevel.WARNING
            import kotlin.DeprecationLevel.HIDDEN
            import java.lang.annotation.ElementType
            import kotlin.DeprecationLevel
            import kotlin.annotation.AnnotationTarget

            @kotlin.annotation.Target(kotlin.annotation.AnnotationTarget.FUNCTION)
            annotation class Dep(
                val message: String = "",
                val message1: String = "",
                val level: DeprecationLevel = DeprecationLevel.WARNING
            )

            typealias LOL = Deprecated
            typealias DL = DeprecationLevel

            class A {
                @Deprecated("", ReplaceWith("a"), HIDDEN)
                fun a() {}

                @Deprecated(message = "", level = HIDDEN)
                fun b() {}

                @Deprecated(message = "", replaceWith = ReplaceWith(""), level = DeprecationLevel.HIDDEN)
                fun c() {}

                @Deprecated(message = "", replaceWith = ReplaceWith(""), level = DeprecationLevel.WARNING)
                fun d() {}

                @Deprecated(message = "", replaceWith = ReplaceWith(""), level = WARNING)
                fun e() {}

                @Deprecated(message = "", replaceWith = ReplaceWith(""))
                fun f() {}

                @Deprecated("")
                fun g() {}

                @Deprecated(message = "", level = WARNING)
                fun h() {}

                @Dep(level = DeprecationLevel.HIDDEN)
                fun i() {}

                @Dep("", "", DeprecationLevel.HIDDEN)
                fun j() {}

                @LOL(level = HIDDEN, message="")
                fun k() {}

                @Deprecated(level = DL.HIDDEN, message="")
                fun l() {}
            }
        """.trimIndent()
        )
        myFixture.testHighlighting("test.kt")

        val methods = myFixture.findClass("A").methods.map { it.name }.sorted()
        TestCase.assertEquals(listOf("A","d","e","f","g","h","i","j"), methods)
    }

    fun testBooleanAnnotationDefaultValue() {
        myFixture.addClass(
            """
            import java.lang.annotation.ElementType;
            import java.lang.annotation.Target;

            @Target(ElementType.FIELD)
            public @interface Autowired {
                boolean required() default true;
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            class AnnotatedClass{
                    @Autowired
                    lateinit var bean: String
            }
        """.trimIndent()
        )
        myFixture.testHighlighting("Autowired.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").fields.single()
            .expectAnnotations(1).single { it.qualifiedName == "Autowired" }
        val annotationAttributeVal = annotations.findAttributeValue("required") as PsiElement
        assertTextRangeAndValue("true", true, annotationAttributeVal)
    }

    fun testStringAnnotationWithUnnamedParameter() {
        myFixture.addClass(
            """
            import java.lang.annotation.ElementType;
            import java.lang.annotation.Target;

            @Target(ElementType.PARAMETER)
            public @interface Qualifier {
                String value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            class AnnotatedClass {
                    fun bar(@Qualifier("foo") param: String){}
            }
        """.trimIndent()
        )
        myFixture.testHighlighting("Qualifier.java", "AnnotatedClass.kt")

        val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single()
            .expectAnnotations(2).single { it.qualifiedName == "Qualifier" }
        val annotationAttributeVal = annotation.findAttributeValue("value") as PsiExpression
        assertTextRangeAndValue("\"foo\"", "foo", annotationAttributeVal)
        TestCase.assertEquals(
            PsiType.getJavaLangString(psiManager, GlobalSearchScope.everythingScope(project)),
            annotationAttributeVal.type
        )
    }

    fun testAnnotationsInAnnotationsDeclarations() {
        myFixture.addClass(
            """
            public @interface OuterAnnotation {
                InnerAnnotation attribute();
                @interface InnerAnnotation {
                }
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation())
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
        assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
    }

    fun testConstants() {
        myFixture.addClass(
            """
            public @interface StringAnnotation {
                  String value();
            }
        """.trimIndent()
        )
        myFixture.addClass(
            """
            public class Constants {

                public static final String MY_CONSTANT = "67";
            }

        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @StringAnnotation(Constants.MY_CONSTANT)
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
        assertTextAndRange("Constants.MY_CONSTANT", annotationAttributeVal)
        TestCase.assertEquals("67", annotationAttributeVal.value)
        TestCase.assertEquals(
            PsiType.getJavaLangString(psiManager, GlobalSearchScope.everythingScope(project)),
            (annotationAttributeVal as PsiExpression).type
        )
    }


    fun testLiteralReplace() {
        myFixture.addClass(
            """
            public @interface StringAnnotation {
                  String value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @StringAnnotation("oldValue")
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
        assertTextAndRange("\"oldValue\"", annotationAttributeVal)
        TestCase.assertEquals("oldValue", annotationAttributeVal.value)
        runWriteAction {
            CommandProcessor.getInstance().runUndoTransparentAction {
                annotationAttributeVal.replace(
                    JavaPsiFacade.getElementFactory(project).createExpressionFromText("\"newValue\"", annotationAttributeVal)
                )
            }
        }
        myFixture.checkResult(
            """
            @StringAnnotation("newValue")
            open class AnnotatedClass
        """.trimIndent()
        )
    }

    fun testReferences() {
        myFixture.addClass(
            """
            public @interface StringAnnotation {
                  String value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @StringAnnotation("someText")
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
        assertTextAndRange("\"someText\"", annotationAttributeVal)
        TestCase.assertTrue(
            "String literal references should be available via light literal",
            annotationAttributeVal.references.any {
                /* FileReferences are injected in every string, so we use them as indicator that KtElement references are available there */
                it is FileReference
            })
    }

    fun testClassLiteral() {
        myFixture.addClass(
            """
            public @interface ClazzAnnotation {
                  Class<?> cls();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @ClazzAnnotation(cls = String::class)
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("cls") as PsiClassObjectAccessExpression
        assertTextAndRange("String::class", annotationAttributeVal)
        TestCase.assertEquals(
            PsiType.getJavaLangString(myFixture.psiManager, GlobalSearchScope.everythingScope(project)),
            annotationAttributeVal.operand.type
        )
    }

    fun testArrayOfClassLiterals() {
        myFixture.addClass(
            """
            public @interface ClazzAnnotation {
                  Class<?>[] cls();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @ClazzAnnotation(cls = [String::class, Throwable::class, ShortArray::class, Array<Array<Int>>::class, Long::class, Unit::class])
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("cls") as KtLightPsiArrayInitializerMemberValue
        assertTextAndRange(
            "[String::class, Throwable::class, ShortArray::class, Array<Array<Int>>::class, Long::class, Unit::class]",
            annotationAttributeVal
        )
        val classLiterals = annotationAttributeVal.initializers.toList().map { it as PsiClassObjectAccessExpression }
        val scope = GlobalSearchScope.everythingScope(project)
        TestCase.assertEquals(
            listOf(
                PsiType.getJavaLangString(myFixture.psiManager, scope),
                PsiType.getTypeByName("java.lang.Throwable", project, scope),
                PsiTypes.shortType().createArrayType(),
                PsiType.getTypeByName("java.lang.Integer", project, scope).createArrayType().createArrayType(),
                PsiTypes.longType(),
                PsiType.getTypeByName("kotlin.Unit", project, scope)
            ),
            classLiterals.map { it.operand.type }
        )
    }

    fun testAnnotationsInAnnotationsArrayDeclarations() {
        myFixture.addClass(
            """
            public @interface OuterAnnotation {
                InnerAnnotation[] attributes();
                @interface InnerAnnotation {
                }
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @OuterAnnotation(attributes = arrayOf(OuterAnnotation.InnerAnnotation()))
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("attributes") as PsiArrayInitializerMemberValue
        assertTextAndRange("arrayOf(OuterAnnotation.InnerAnnotation())", annotationAttributeVal)
        assertTextAndRange("InnerAnnotation()", annotationAttributeVal.initializers[0])
    }


    fun testAnnotationsInAnnotationsFinalDeclarations() {
        myFixture.addClass(
            """
            public @interface OuterAnnotation {
                InnerAnnotation attribute();
                @interface InnerAnnotation {
                }
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation())
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
        assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
    }

    fun testAnnotationsInAnnotationsInAnnotationsDeclarations() {
        myFixture.addClass(
            """
            public @interface OuterAnnotation {
                InnerAnnotation attribute();
                @interface InnerAnnotation {
                    InnerInnerAnnotation attribute();
                    @interface InnerInnerAnnotation {
                    }
                }
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation(attribute = OuterAnnotation.InnerAnnotation.InnerInnerAnnotation()))
            open class AnnotatedClass //There is another exception if class is not open
        """.trimIndent()
        )
        myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
        assertTextAndRange("InnerAnnotation(attribute = OuterAnnotation.InnerAnnotation.InnerInnerAnnotation())", annotationAttributeVal)

        annotationAttributeVal as PsiAnnotation
        val innerAnnotationAttributeVal = annotationAttributeVal.findAttributeValue("attribute") as PsiElement
        assertTextAndRange("InnerInnerAnnotation()", innerAnnotationAttributeVal)
    }

    fun testKotlinAnnotations() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val anno2: Anno2)
            annotation class Anno2(val anno3: Anno3)
            annotation class Anno3

            @Anno1(Anno2(Anno3()))
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("anno2") as PsiElement
        assertTextAndRange("Anno2(Anno3())", annotationAttributeVal)

        annotationAttributeVal as PsiAnnotation
        val innerAnnotationAttributeVal = annotationAttributeVal.findAttributeValue("anno3") as PsiElement
        assertTextAndRange("Anno3()", innerAnnotationAttributeVal)
        assertIsKtLightAnnotation("Anno3()", innerAnnotationAttributeVal)
    }

    fun testKotlinAnnotationWithStringArray() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno(val params: Array<String>)
            @Anno(arrayOf("abc", "def"))
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("params") as PsiElement
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        TestCase.assertTrue(annotations.first().parameterList.attributes.any { (it.value == annotationAttributeVal) })
        assertTextAndRange("arrayOf(\"abc\", \"def\")", annotationAttributeVal)

        annotationAttributeVal as PsiArrayInitializerMemberValue
        assertTextAndRange("\"abc\"", annotationAttributeVal.initializers[0])
        assertTextAndRange("\"def\"", annotationAttributeVal.initializers[1])
    }

    fun testKotlinAnnotationWithStringArrayLiteral() {
        configureKotlinVersion(IdeKotlinVersion.get("1.2.0"))
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno(val params: Array<String>)
            @Anno(params = ["abc", "def"])
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("params") as PsiElement
        assertTextAndRange("[\"abc\", \"def\"]", annotationAttributeVal)

        annotationAttributeVal as PsiArrayInitializerMemberValue
        assertTextAndRange("\"abc\"", annotationAttributeVal.initializers[0].assertInstanceOf<PsiLiteral>())
        assertTextAndRange("\"def\"", annotationAttributeVal.initializers[1])
    }


    fun testKotlinAnnotationsArray() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val anno2: Array<Anno2>)
            annotation class Anno2(val v:Int)

            @Anno1(anno2 = arrayOf(Anno2(1), Anno2(2)))
            class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("AnnotatedClass.kt")

        val annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single()

        val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue
        annotationAttributeVal.parent.assertInstanceOf<PsiNameValuePair>().let { pair ->
            TestCase.assertEquals("anno2", pair.name)
        }

        assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
        annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
            assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
            assertIsKtLightAnnotation("Anno2(1)", innerAnnotationAttributeVal)
            innerAnnotationAttributeVal as PsiAnnotation
            val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf<PsiLiteral>()
            assertTextAndRange("1", value)
            TestCase.assertEquals(PsiTypes.intType(), value.assertInstanceOf<PsiExpression>().type)
        }


        val attributeValueFromParameterList = annotation.parameterList.attributes.single().value as PsiArrayInitializerMemberValue
        assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", attributeValueFromParameterList)
        attributeValueFromParameterList.initializers[0].let { innerAnnotationAttributeVal ->
            assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
            assertIsKtLightAnnotation("Anno2(1)", innerAnnotationAttributeVal)
        }

    }

    fun testVarargAnnotation() {

        myFixture.configureByText(
            "Outer.java", """
            @interface Outer{
                Inner[] value() default {};
            }

            @interface Inner{
            }
        """
        )
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Outer(Inner())
            class MyAnnotated {}
        """.trimIndent()
        )

        val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
        annotations[0].let { annotation ->
            val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
            assertTextAndRange("Inner()", annotationAttributeVal)
            annotationAttributeVal as PsiArrayInitializerMemberValue
            annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
                assertTextAndRange("Inner()", innerAnnotationAttributeVal)
                assertIsKtLightAnnotation("Inner()", innerAnnotationAttributeVal)
            }
            UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
            TestCase.assertTrue(annotation.parameterList.attributes.any { it.value == annotationAttributeVal })
        }

    }

    fun testKtVarargAnnotation() {

        myFixture.configureByText(
            "Anno.kt", """
            annotation class Anno(vararg val vars: String)
        """
        )
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Anno("hello", "world")
            class MyAnnotated
        """.trimIndent()
        )

        val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("vars")
        annotationAttributeVal as PsiArrayInitializerMemberValue
        assertTextAndRange("(\"hello\", \"world\")", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        UsefulTestCase.assertEquals(annotationAttributeVal.initializers.size, 2)
        assertTextAndRange("\"hello\"", annotationAttributeVal.initializers[0])
        assertTextAndRange("\"world\"", annotationAttributeVal.initializers[1])
    }

    private fun doVarargTest(type: String, parameters: List<String>) {
        val paramsJoined = parameters.joinToString(", ")

        myFixture.addClass(
            """
                public @interface Annotation {
                    $type[] value() default {};
                }
            """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
                @Annotation($paramsJoined)
                open class AnnotatedClass
            """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("($paramsJoined)", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        for ((i, arg) in parameters.withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }


    fun testVarargInt() = doVarargTest("int", listOf("1", "2", "3"))

    fun testVarargClasses() = doVarargTest("""Class<?>""", listOf("Any::class", "String::class", "Int::class"))

    fun testVarargWithSpread() {
        myFixture.addClass(
            """
            public @interface Annotation {
                String[] value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Annotation(value = *arrayOf("a", "b", "c"))
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("arrayOf(\"a\", \"b\", \"c\")", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }

    fun testVarargWithSpreadComplex() {
        myFixture.addClass(
            """
            public @interface Annotation {
                String[] value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Annotation(value = arrayOf(*arrayOf("a", "b"), "c", *arrayOf("d", "e")))
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("arrayOf(*arrayOf(\"a\", \"b\"), \"c\", *arrayOf(\"d\", \"e\"))", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        for ((i, arg) in listOf("arrayOf(\"a\", \"b\")", "\"c\"", "arrayOf(\"d\", \"e\")").withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }

    fun testVarargWithArrayLiteral() {
        myFixture.addClass(
            """
            public @interface Annotation {
                String[] value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Annotation(value = ["a", "b", "c"])
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("[\"a\", \"b\", \"c\"]", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }

    fun testVarargWithSingleArg() {
        myFixture.addClass(
            """
            public @interface Annotation {
                String[] value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Annotation("a")
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("\"a\"", annotationAttributeVal)
        UsefulTestCase.assertInstanceOf(annotationAttributeVal.parent, PsiNameValuePair::class.java)
        for ((i, arg) in listOf("\"a\"").withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }

    fun testVarargWithArrayLiteralAndSpread() {
        myFixture.addClass(
            """
            public @interface Annotation {
                String[] value();
            }
        """.trimIndent()
        )

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @Annotation(*["a", "b", "c"])
            open class AnnotatedClass
        """.trimIndent()
        )
        myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt")

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue
        assertTextAndRange("[\"a\", \"b\", \"c\"]", annotationAttributeVal)
        for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) {
            assertTextAndRange(arg, annotationAttributeVal.initializers[i])
        }
    }

    @Suppress("UNREACHABLE_CODE")
    fun testRepeatableAnnotationsArray() {
        //See https://youtrack.jetbrains.com/issue/KT-34107
        return

        myFixture.configureByText(
            "RAnno.java", """
            import java.lang.annotation.Repeatable;

            @interface RContainer{
                RAnno[] value() default {};
            }

            @Repeatable(RContainer.class)
            public @interface RAnno {
                String[] value() default {};
            }
        """
        )
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            @RAnno()
            @RAnno("1")
            @RAnno("1", "2")
            class AnnotatedClass
        """.trimIndent()
        )

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(3)
        annotations[0].let { annotation ->
            val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
            assertTextAndRange("{}", annotationAttributeVal)
            annotationAttributeVal as PsiArrayInitializerMemberValue
            TestCase.assertTrue(annotationAttributeVal.initializers.isEmpty())
        }
        annotations[1].let { annotation ->
            val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
            assertTextAndRange("\"1\"", annotationAttributeVal)
            annotationAttributeVal as PsiArrayInitializerMemberValue
            annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
                assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
            }
        }
        annotations[2].let { annotation ->
            val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
            assertTextAndRange("(\"1\", \"2\")", annotationAttributeVal)
            annotationAttributeVal as PsiArrayInitializerMemberValue
            annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
                assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
            }
            annotationAttributeVal.initializers[1].let { innerAnnotationAttributeVal ->
                assertTextAndRange("\"2\"", innerAnnotationAttributeVal)
            }
        }

    }

    fun testJavaKeywordsInName() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            package my.public.place

            annotation class Anno1(val import: String)

            @Anno1(import = "full")
            class AnnotatedClass
        """.trimIndent()
        )

        val annotations = myFixture.findClass("my.public.place.AnnotatedClass").expectAnnotations(1)
        val annotation = annotations.first()
        TestCase.assertEquals("my.public.place.Anno1", annotation.qualifiedName)
        assertTextAndRange("\"full\"", annotation.findAttributeValue("import")!!)
    }

    fun testWrongNamesPassed() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val i:Int , val j: Int)

            @Anno1(k = 3, l = 5)
            class AnnotatedClass
        """.trimIndent()
        )

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotation = annotations.first()
        TestCase.assertNull(annotation.findAttributeValue("k"))
        TestCase.assertNull(annotation.findAttributeValue("l"))
        TestCase.assertNull(annotation.findAttributeValue("i"))
        TestCase.assertNull(annotation.findAttributeValue("j"))
    }

    fun testWrongValuesPassed() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val i: Int , val j: Int)

            @Anno1(i = true, j = false)
            class AnnotatedClass
        """.trimIndent()
        )

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotation = annotations.first()
        assertTextAndRange("true", annotation.findAttributeValue("i")!!)
        assertTextAndRange("false", annotation.findAttributeValue("j")!!)
    }

    fun testDuplicateParameters() {
        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val i:Int , val i: Boolean)

            @Anno1(i = true, i = 3)
            class AnnotatedClass
        """.trimIndent()
        )

        val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        val annotation = annotations.first()
        assertTextAndRange("true", annotation.findAttributeValue("i")!!)
    }

    @Suppress("UNREACHABLE_CODE")
    fun testMissingDefault() {
        //See https://youtrack.jetbrains.com/issue/KT-34107
        return

        myFixture.configureByText(
            "AnnotatedClass.kt", """
            annotation class Anno1(val i: Int = 0)

            @Anno1()
            class AnnotatedClass
        """.trimIndent()
        )

        val (annotation) = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
        assertTextAndRange("0", annotation.findAttributeValue("i")!!)
    }

    private fun assertTextAndRange(expected: String, psiElement: PsiElement) {
        TestCase.assertEquals("mismatch for $psiElement of ${psiElement.javaClass}", expected, psiElement.text)
        TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text))
        TestCase.assertEquals(psiElement, PsiAnchor.create(psiElement).retrieve())
    }

    private fun assertIsKtLightAnnotation(expected: String, psiElement: PsiElement) {
        TestCase.assertEquals(expected, (psiElement as KtLightAnnotationForSourceEntry).kotlinOrigin.text)
    }

    private fun assertTextRangeAndValue(expected: String, value: Any?, psiElement: PsiElement) {
        assertTextAndRange(expected, psiElement)
        val result = JavaPsiFacade.getInstance(project).constantEvaluationHelper.computeConstantExpression(psiElement)
        TestCase.assertEquals(value, result)
        val smartPointer = SmartPointerManager.getInstance(psiElement.project).createSmartPsiElementPointer(psiElement)
        assertTextAndRange(expected, smartPointer.element!!)
    }

    private fun PsiModifierListOwner.expectAnnotations(number: Int): Array<PsiAnnotation> =
        this.modifierList!!.annotations.apply {
            assertEquals(
                "expected $number annotation(s), found [${this.joinToString(", ") { it.qualifiedName ?: "unknown" }}]",
                number, size
            )
        }

    private fun configureKotlinVersion(version: IdeKotlinVersion) {
        WriteAction.run<Throwable> {
            val modelsProvider = ProjectDataManager.getInstance().createModifiableModelsProvider(project)
            val facet = module.getOrCreateFacet(modelsProvider, useProjectSettings = false)
            facet.configureFacet(version, null, modelsProvider)
            modelsProvider.commit()
        }
    }

}
